from datetime import datetime
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from base64 import decodebytes, encodebytes
import json
import os

# 尝试导入 Crypto 库（需要安装 pycryptodome）
# 安装命令: pip install pycryptodome
# 注意：如果已安装 pycrypto 或 crypto（小写），需要先卸载
try:
    from Crypto.PublicKey import RSA
    from Crypto.Signature import PKCS1_v1_5
    from Crypto.Hash import SHA256
except ImportError as e:
    import sys
    import os
    # 检查是否有冲突的小写 crypto 目录
    site_packages = None
    for path in sys.path:
        if 'site-packages' in path:
            site_packages = path
            break
    
    error_msg = (
        "Crypto 库未正确安装或存在冲突。\n\n"
        "解决方案：\n"
        "1. 卸载冲突的包:\n"
        "   pip uninstall -y crypto pycrypto\n\n"
        "2. 重新安装 pycryptodome:\n"
        "   pip install --force-reinstall pycryptodome\n\n"
    )
    
    if site_packages:
        crypto_lower = os.path.join(site_packages, 'crypto')
        crypto_upper = os.path.join(site_packages, 'Crypto')
        if os.path.exists(crypto_lower):
            error_msg += (
                f"3. 检测到冲突：存在小写的 'crypto' 目录: {crypto_lower}\n"
                "   在 Windows 上，这会导致与 'Crypto' 冲突。\n"
                "   请手动删除该目录或重启 Python 解释器后重试。\n"
            )
        if os.path.exists(crypto_upper) and not os.path.exists(os.path.join(crypto_upper, '__init__.py')):
            error_msg += (
                f"4. Crypto 目录存在但缺少 __init__.py 文件: {crypto_upper}\n"
                "   请重新安装 pycryptodome。\n"
            )
    
    raise ImportError(error_msg) from e


class AliPay(object):
    """
    支付宝支付接口(PC端支付接口)
    """

    def __init__(self, appid, app_notify_url, app_private_key_path,
                 alipay_public_key_path, return_url, debug=False):
        self.appid = appid
        self.app_notify_url = app_notify_url
        self.app_private_key_path = app_private_key_path
        self.app_private_key = None
        self.return_url = return_url

        # Allow both PEM strings and file paths for keys
        self.app_private_key = self._load_rsa_key(app_private_key_path, is_private=True)
        self.alipay_public_key_path = alipay_public_key_path
        self.alipay_public_key = self._load_rsa_key(alipay_public_key_path, is_private=False)

        if debug is True:
            # 支付宝沙箱环境网关地址
            # 注意：如果遇到 502 错误，可能需要检查支付宝官方文档确认最新的沙箱网关地址
            # 可能的地址：
            # - https://openapi-sandbox.dl.alipaydev.com/gateway.do (新版本)
            # - https://openapi.alipaydev.com/gateway.do (旧版本，可能已废弃)
            self.__gateway = "https://openapi-sandbox.dl.alipaydev.com/gateway.do"
        else:
            # 生产环境网关地址
            self.__gateway = "https://openapi.alipay.com/gateway.do"

    @property
    def gateway(self):
        """获取支付宝网关地址"""
        return self.__gateway

    def _ensure_pem_wrapped(self, key_str: str, is_private: bool) -> str:
        """
        If the key string is a bare base64 (no -----BEGIN ...-----), wrap it with proper PEM headers.
        """
        if "-----BEGIN" in key_str:
            return key_str
        header = "-----BEGIN PRIVATE KEY-----" if is_private else "-----BEGIN PUBLIC KEY-----"
        footer = "-----END PRIVATE KEY-----" if is_private else "-----END PUBLIC KEY-----"
        # chunk into 64-char lines per PEM convention
        chunks = [key_str[i:i+64] for i in range(0, len(key_str), 64)]
        body = "\n".join(chunks)
        return f"{header}\n{body}\n{footer}"

    def _load_rsa_key(self, key_source: str, is_private: bool):
        """
        Load an RSA key from either a PEM string or a file path.
        """
        # If looks like a path and exists, read file
        if isinstance(key_source, str) and os.path.exists(key_source):
            with open(key_source, "r", encoding="utf-8") as fp:
                content = fp.read()
            return RSA.import_key(content)
        # Otherwise treat as PEM/base64 content
        pem = self._ensure_pem_wrapped(key_source, is_private=is_private)
        return RSA.import_key(pem)

    def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
        biz_content = {
            "subject": subject,
            "out_trade_no": out_trade_no,
            "total_amount": total_amount,
            "product_code": "FAST_INSTANT_TRADE_PAY",
            # "qr_pay_mode":4
        }

        biz_content.update(kwargs)
        data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
        return self.sign_data(data)

    # 查询接口
    def query_pay(self, out_trade_no, return_url=None, **kwargs):
        biz_content = {
            "out_trade_no": out_trade_no,

            # "product_code": "FAST_INSTANT_TRADE_PAY",
            # "qr_pay_mode":4
        }

        biz_content.update(kwargs)
        data = self.build_body("alipay.trade.query", biz_content, self.return_url)
        return self.sign_data(data)

    def build_body(self, method, biz_content, return_url=None):
        data = {
            "app_id": self.appid,
            "method": method,
            "charset": "utf-8",
            "sign_type": "RSA2",
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "version": "1.0",
            "biz_content": biz_content
        }

        if return_url is not None:
            data["notify_url"] = self.app_notify_url
            data["return_url"] = self.return_url

        return data

    def sign_data(self, data):
        """
        对数据进行签名并生成最终的查询参数字符串
        """
        # 创建数据副本，避免修改原始数据
        data_copy = data.copy()
        data_copy.pop("sign", None)
        
        # 排序后的字符串
        unsigned_items = self.ordered_data(data_copy)
        
        # 生成用于签名的字符串（不进行URL编码）
        unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
        
        # 计算签名
        sign = self.sign(unsigned_string.encode("utf-8"))
        
        # 生成用于URL的字符串（进行URL编码）
        quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)

        # 获得最终的订单信息字符串
        signed_string = quoted_string + "&sign=" + quote_plus(sign)
        
        # 调试信息：打印签名字符串（仅用于调试，生产环境应移除）
        import sys
        if hasattr(sys, '_getframe') and sys._getframe(1).f_globals.get('__name__') == '__main__':
            print(f"[DEBUG] 签名字符串: {unsigned_string}")
            print(f"[DEBUG] 签名结果: {sign[:50]}...")
        
        return signed_string

    def ordered_data(self, data):
        """
        对数据进行排序并处理复杂类型（如字典）
        注意：创建数据副本，避免修改原始数据
        """
        # 创建数据副本，避免修改原始数据
        data_copy = data.copy()
        complex_keys = []
        
        for key, value in data_copy.items():
            if isinstance(value, dict):
                complex_keys.append(key)

        # 将字典类型的数据dump出来
        # 注意：支付宝要求使用 ensure_ascii=True（默认），将中文转换为 \uXXXX 格式
        for key in complex_keys:
            data_copy[key] = json.dumps(data_copy[key], separators=(',', ':'), ensure_ascii=True)

        return sorted([(k, v) for k, v in data_copy.items()])

    def sign(self, unsigned_string):
        """
        使用RSA2算法对字符串进行签名
        """
        try:
            key = self.app_private_key
            signer = PKCS1_v1_5.new(key)
            # 使用 SHA256 哈希算法
            hash_obj = SHA256.new(unsigned_string)
            signature = signer.sign(hash_obj)
            # base64 编码，转换为unicode表示并移除回车
            sign = encodebytes(signature).decode("utf8").replace("\n", "")
            return sign
        except Exception as e:
            raise Exception(f"签名生成失败: {str(e)}")

    def _verify(self, raw_content, signature):
        # 开始计算签名
        key = self.alipay_public_key
        signer = PKCS1_v1_5.new(key)
        digest = SHA256.new()
        digest.update(raw_content.encode("utf8"))
        if signer.verify(digest, decodebytes(signature.encode("utf8"))):
            return True
        return False

    def verify(self, data, signature):
        if "sign_type" in data:
            sign_type = data.pop("sign_type")
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)

        message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
        return self._verify(message, signature)
